public class LinkedListProblem {

    //1. 删除链表中等于给定值val的所有节点。
    // OJ链接:https://leetcode-cn.com/problems/remove-linked-list-elements/description/
    //203. 移除链表元素
    //给你一个链表的头节点 head 和一个整数 val ，
    // 请你删除链表中所有满足 Node.val == val 的节点，并返回 新的头节点 。
    class ListNode {
        int val;
        ListNode next;
        ListNode() {}
        ListNode(int val) { this.val = val; }
        ListNode(int val, ListNode next) { this.val = val; this.next = next; }
    }
    public ListNode removeElements(ListNode head, int val) {
        //1.判定链表是否为空
        if(head == null) {
            return null;
        }
        //2.从第2个节点开始遍历链表，节点的值等于val，则删除该节点前节点，否则移动到下一个节点
        ListNode cur = head;
        while(cur.next!= null){
            if(cur.next.val == val){
                cur.next = cur.next.next;
            }else{
                cur = cur.next;
            }
        }
        //3.判断头节点是否等于val，如果等于val，则返回头节点的下一个节点，否则返回头节点
        if(head.val == val){
            head = head.next;
            return head;
        }else{
            return head;
        }
    }


    //2. 反转⼀个单链表。
    //OJ链接:https://leetcode-cn.com/problems/reverse-linked-list/description/
    //206. 反转链表
    //给你单链表的头节点 head ，请你反转链表，并返回反转后的链表。
    public ListNode reverseList(ListNode head) {
        //1.判定链表是否为空
        if(head == null) {
            return null;
        }
        //2.如果链表只有一个节点，则直接返回该节点
        if(head.next == null) {
            return head;
        }
        //3.反转链表，定义三个指针，pre指向null，cur指向head，nex指向null
        ListNode pre = null;
        ListNode cur = head;
        ListNode nex = null;
        //4.遍历链表，将cur的next指针指向pre，pre指向cur，cur指向nex，nex指向null
        while(cur!=null){
            nex=cur.next;
            cur.next=pre;
            pre=cur;
            cur=nex;
        }
        //5.返回反转后的链表
        return pre;
    }


    //3.
    // OJ链接:https://leetcode-cn.com/problems/middle-of-the-linked-list/description/
    //876. 链表的中间结点
    //给你单链表的头结点 head ，请你找出并返回链表的中间结点。
    //如果有两个中间结点，则返回第二个中间结点。
    public ListNode middleNode(ListNode head) {
        //1.判定链表是否为空
        if(head == null){
            return null;
        }
        //2.求链表长度
        int size = size(head);
        //3.求中间节点的位置
        ListNode middle = head;
        for(int i = 0; i < size/2; i++){
            middle = middle.next;
        }
        return middle;
    }


    //4. 输⼊⼀个链表，输出该链表中倒数第k个结点。
    // OJ链接:https://leetcode.cn/problems/kth-node-from-end-of-list-lcci/
    //面试题 02.02. 返回倒数第 k 个节点
    //实现一种算法，找出单向链表中倒数第 k 个节点。返回该节点的值。
    public int kthToLast(ListNode head, int k) {
        //1.判定链表是否为空
        if(head == null){
            return 0;
        }
        //2.求链表长度
        int size = size(head);
        //3.判定k是否有效
        if(k <= 0 || k > size){
            return 0;
        }
        //4.根据k计算倒数第k个节点的下标
        int index = size - k;
        //5.遍历链表，移动引用到第index个节点
        ListNode cur = head;
        for(int i = 0; i < index; i++){
            cur = cur.next;
        }
        //6.返回第index个节点的值
        return cur.val;
    }


    //5.
    // OJ链接:https://leetcode-cn.com/problems/merge-two-sorted-lists/description/
    //21. 合并两个有序链表
    //将两个升序链表合并为一个新的升序链表并返回。新链表是通过拼接给定的两个链表的所有节点组成的。
    public ListNode mergeTwoLists(ListNode list1, ListNode list2) {
        //1.当出现链表为空的情况时
        if(list1 == null){
            return list2;
        }
        if(list2 == null){
            return list1;
        }
        //2.准备两个指针，一个指向list1的头结点，一个指向list2的头结点
        ListNode cur1 = list1;
        ListNode cur2 = list2;
        //3.定义一个新的链表，用于存储合并后的链表
        //这里采用带傀儡节点的方式，后续会更加简单
        ListNode newList = new ListNode(0);
        //4.记录一个尾巴，指向newList的尾结点
        ListNode tail = newList;
        //5.进入循环，开始合并
        while(cur1!= null && cur2!= null){
            //判断cur1和cur2哪个节点的值更小
            if(cur1.val < cur2.val){
                //如果cur1的节点值更小，则将其添加到newList尾巴后
                tail.next = cur1;
                //更新cur1的位置
                cur1 = cur1.next;
            }else{
                //如果cur2的节点值更小，则将其添加到newList尾巴后
                tail.next = cur2;
                //更新cur2的位置
                cur2 = cur2.next;
            }
            //更新尾巴
            tail = tail.next;
        }
        //6.上述循环完毕，意味着其中一个链表已经遍历完，将剩余的链表添加到newList尾巴后
        if(cur1!= null){
            tail.next = cur1;
        }
        if(cur2!= null){
            tail.next = cur2;
        }
        //7.返回newList的头结点
        //注意，这里返回的是newList的头结点的下一个节点，
        // 因为newList的头结点是带傀儡节点
        return newList.next;
    }


    //6.
    //OJ链接：https://www.nowcoder.com/practice/0e27e0b064de4eacac178676ef9c9d70?tpId=8&&tqId=11004&rp=2&ru=/activity/oj&qru=/ta/cracking-the-coding-interview/question-ranking
    //CM11 链表分割
    //现有一链表的头指针 ListNode* pHead，给一定值x，
    // 编写一段代码将所有小于x的结点排在其余结点之前，
    // 且不能改变原来的数据顺序，返回重新排列后的链表的头指针。
    public ListNode partition(ListNode pHead, int x) {
        //1.当链表为空时
        if(pHead == null){
            return null;
        }
        //2.当链表只有一个节点时
        if(pHead.next == null){
            return pHead;
        }
        //3.先创建两个链表，一个存储小于x的结点，一个存储大于等于x的结点
        // 这里采用带傀儡节点的方式，后续会更加简单
        //同时分别记录这两个链表的尾巴
        ListNode smallHead = new ListNode(0);
        ListNode smallTail = smallHead;
        ListNode largeHead = new ListNode(0);
        ListNode largeTail = largeHead;
        //4.遍历原链表，将小于x的结点添加到smallTail后，大于等于x的结点添加到largeTail后
        for(ListNode cur = pHead; cur!= null; cur = cur.next){
            if(cur.val < x){
                smallTail.next = cur;
                smallTail = smallTail.next;
            }else{
                largeTail.next = cur;
                largeTail = largeTail.next;
            }
        }
        //4.将两个链表连接起来
        smallTail.next = largeHead.next;
        //5.稳妥起见，将尾巴的下一个引用指向空
        largeTail.next = null;
        //6.返回新的链表头结点
        return smallHead.next;
    }


    //7.链表的回文结构
    //OJ链接：https://www.nowcoder.com/practice/d281619e4b3e4a60a2cc66ea32855bfa?tpId=49&&tqId=29370&rp=1&ru=/activity/oj&qru=/ta/2016test/question-ranking
    //OR36 链表的回文结构
    //对于一个链表，请设计一个时间复杂度为O(n),额外空间复杂度为O(1)的算法，判断其是否为回文结构。
    //给定一个链表的头指针A，请返回一个bool值，代表其是否为回文结构。保证链表长度小于等于900。
    public boolean chkPalindrome(ListNode A) {
        // 1.当链表为空时
        if (A == null) {
            return true;
        }
        // 2.当链表只有一个节点时
        if (A.next == null) {
            return true;
        }
        // 3.定义两个指针，一个指向头结点，一个指向尾结点
        ListNode slow = A;
        ListNode fast = A;
        // 4.快指针走两步，慢指针走一步
        while (fast.next!= null && fast.next.next!= null) {
            slow = slow.next;
            fast = fast.next.next;
        }
        // 5.将链表分成两部分，前半部分和后半部分
        //reverseList(slow.next)意思是反转了一下链表，然后返回反转后的链表的头结点
        ListNode secondHalf = reverseList(slow.next);
        ListNode firstHalf = A;
        // 6.判断是否为回文结构
        while (secondHalf!= null) {
            if (firstHalf.val != secondHalf.val) {
                return false;
            }
            firstHalf = firstHalf.next;
            secondHalf = secondHalf.next;
        }
        return true;
    }


    //8.输入两个链表，找出它们的第一个公共结点。
    //OJ链接：https://leetcode-cn.com/problems/intersection-of-two-linked-lists/description/
    //160. 相交链表
    //给你两个单链表的头节点 headA 和 headB ，
    // 请你找出并返回两个链表相交的起始节点
    // 。如果两个链表没有交点，返回 null 。
    public ListNode getIntersectionNode(ListNode headA, ListNode headB) {
         //1.当两个链表都为空时
        if(headA == null || headB == null){
            return null;
        }
        //2.当两个链表只有一个节点时
        if(headA.next == null || headB.next == null){
            return null;
        }
        //3.定义两个指针，一个指向headA，一个指向headB
        ListNode curA = headA;
        ListNode curB = headB;
        //4.遍历两个链表，当两个指针指向同一个节点时，返回该节点
        while(curA != curB){
            //如果curA指向null，则将curA指向headB
            if(curA == null){
                curA = headB;
            }else{
                curA = curA.next;
            }
            //如果curB指向null，则将curB指向headA
            if(curB == null){
                curB = headA;
            }else{
                curB = curB.next;
            }
        }
        return curA;
    }


    //9.给定⼀个链表，判断链表中是否有环。
    // OJ链接：https://leetcode-cn.com/problems/linked-list-cycle/description/
    //141. 环形链表
    //给你一个链表的头节点 head ，判断链表中是否有环。
    //如果链表中有某个节点，可以通过连续跟踪 next 指针再次到达，则链表中存在环。
    //为了表示给定链表中的环，评测系统内部使用整数 pos 来表示
    // 链表尾连接到链表中的位置（索引从 0 开始）。
    // 注意：pos 不作为参数进行传递 。仅仅是为了标识链表的实际情况。
    public boolean hasCycle(ListNode head) {
        //1.当链表为空时
        if(head == null){
            return false;
        }
        //使用快慢引用的方法，判断链表是否有环
        ListNode slow = head;
        ListNode fast = head.next;
        while(fast != null && fast.next != null){
            if(slow == fast){
                return true;
            }
            slow = slow.next;
            fast = fast.next.next;
        }
        return false;
    }


    //10. 给定⼀个链表，返回链表开始⼊环的第⼀个节点。如果链表⽆环，则返回NULL
    // OJ链接:https://leetcode-cn.com/problems/linked-list-cycle-ii/description/
    //142. 环形链表 II
    //给定一个链表，返回链表开始入环的第一个节点。
    // 如果链表无环，则返回 null 。
    //为了表示给定链表中的环，评测系统内部使用整数 pos 来表示链表尾连接到链表中的位置（索引从 0 开始）。
    // 注意，pos 不作为参数进行传递。仅仅是为了标识链表的实际情况。
    public ListNode detectCycle(ListNode head) {
        if (head == null) {
            return null;
        }
        // 1. 使用快慢指针, 找到相遇位置. 如果不相遇, 说明无环.
        ListNode slow = head;
        ListNode fast = head;
        while (fast != null && fast.next != null) {
            fast = fast.next.next;
            slow = slow.next;
            if (fast == slow) {
                break;
            }
        }
        // 循环结束, 需要判定, 是因为 fast 和 slow 重合, 结束循环的, 还是因为 fast 为 null 结束循环的.
        if (fast == null || fast.next == null) {
            // 无环的情况, fast 已经到末尾了.
            return null;
        }
        // 2. 设置一个 cur1 , 从 head 出发, 设置 cur2 从交汇位置出发. 两个引用同时往前走.
        //    两个引用重合的位置, 就是环的入口点.
        ListNode cur1 = head;
        ListNode cur2 = slow;
        // 这里的循环一定不会死循环的. 通过数学公式已经证明了, cur1 和 cur2 是必定会相遇.
        // 而且相遇的位置就是环的入口.
        while (cur1 != cur2) {
            cur1 = cur1.next;
            cur2 = cur2.next;
        }
        // 循环结束, 说明 cur1 和 cur2 相遇了.
        return cur1;
    }


    public int size(ListNode head){
        int size = 0;
        for(ListNode cur = head; cur!= null; cur = cur.next){
            size++;
        }
        return size;
    }
}
